#pragma once

#include <wrl/client.h>
#include <ppl.h>
#include <ppltasks.h>
#include <d3d11_1.h>
#include <d2d1_1.h>

namespace DXBase
{
	ref class DXContext;

	inline HRESULT HR2Ex(HRESULT hr)
	{
		if (FAILED(hr))
		{
			// Set a breakpoint on this line to catch Win32 API errors.
			throw Platform::Exception::CreateException(hr);
		}
		return hr;
	}

	// Function that reads from a binary file asynchronously.
	inline Concurrency::task<Platform::Array<byte>^> ReadDataAsync(Platform::String^ filename)
	{
		using namespace Windows::Storage;
		using namespace Concurrency;
		
		auto folder = Windows::ApplicationModel::Package::Current->InstalledLocation;
		
		return create_task(folder->GetFileAsync(filename)).then([] (StorageFile^ file) 
		{
			return FileIO::ReadBufferAsync(file);
		}).then([] (Streams::IBuffer^ fileBuffer) -> Platform::Array<byte>^ 
		{
			auto fileData = ref new Platform::Array<byte>(fileBuffer->Length);
			Streams::DataReader::FromBuffer(fileBuffer)->ReadBytes(fileData);
			return fileData;
		});
	}

	Platform::String^ GetFileExtension(_In_ Platform::String^ filename);

	// when in plain C++ app I had problem with this event...
	void RegisterPropertyChangedSafely(
		Windows::UI::Xaml::Data::INotifyPropertyChanged^ src, 
		Windows::UI::Xaml::Data::PropertyChangedEventHandler^ handler, 
		Windows::Foundation::EventRegistrationToken& token);

	
	template<class T>
	Microsoft::WRL::ComPtr<ID3D11Buffer> CreateBuffer(ID3D11Device1* device, unsigned int num, T* data, D3D11_BIND_FLAG bindFlags)
	{
		assert(device);
		assert(data);

		Microsoft::WRL::ComPtr<ID3D11Buffer> result;

		D3D11_BUFFER_DESC desc;
		desc.ByteWidth = sizeof(T) * num;
		desc.Usage = D3D11_USAGE_DEFAULT;
		desc.BindFlags = bindFlags;
		desc.CPUAccessFlags = 0;
		desc.MiscFlags = 0;
		desc.StructureByteStride = 0;

		D3D11_SUBRESOURCE_DATA bufdata;
		bufdata.pSysMem = data;
		bufdata.SysMemPitch = 0;
		bufdata.SysMemSlicePitch = 0;

		HR2Ex(device->CreateBuffer(&desc, &bufdata, &result));
		return result;
	}

	template <class T>
	struct ConstantBuffer
	{
		T Data;
		Microsoft::WRL::ComPtr<ID3D11Buffer> buffer;
		void CreateBuffer(ID3D11Device1* device, D3D11_BIND_FLAG bindflag = D3D11_BIND_CONSTANT_BUFFER)
		{
			assert(device);
			buffer = DXBase::CreateBuffer<T>(device, 1, &Data, bindflag);
		}
		void UpdateBuffer(ID3D11DeviceContext1* ctxt)
		{
			assert(ctxt);
			assert(buffer.Get());
			ctxt->UpdateSubresource(buffer.Get(), 0, NULL, &Data, 0, 0);
		}
		void SetVSData(ID3D11DeviceContext1* ctxt, UINT vsindex)
		{
			assert(ctxt);
			assert(buffer.Get());
			ctxt->UpdateSubresource(buffer.Get(), 0, NULL, &Data, 0, 0);
			ctxt->VSSetConstantBuffers(vsindex, 1, buffer.GetAddressOf());
		}
	};

	template<class T>
	struct ArrayBuffer
	{
		ArrayBuffer()
			: Data(new std::vector<T>())
		{
		}

		std::shared_ptr<std::vector<T>> Data;
		Microsoft::WRL::ComPtr<ID3D11Buffer> buffer;
		void CreateBuffer(ID3D11Device1* device, D3D11_BIND_FLAG bindflag)
		{
			assert(device);
			buffer = DXBase::CreateBuffer<T>(device, Data.size(), &Data->at(0), bindflag);
		}
		void UpdateBuffer(ID3D11DeviceContext1* ctxt)
		{
			assert(ctxt);
			assert(buffer.Get());
			ctxt->UpdateSubresource(buffer.Get(), 0, NULL, &Data->at(0), 0, 0);
		}
	};


	template <class T>
	struct Mapped
	{
		Microsoft::WRL::ComPtr<T> pMapped;
		DXContext^ context;
		D3D11_MAPPED_SUBRESOURCE mapped;

		~Mapped() { Unmap(); }
		void Initialize(DXContext^ ctxt, Microsoft::WRL::ComPtr<T> src)
		{
			Unmap();
			context = ctxt;
			pMapped = src;
		}
		void Map()
		{
			assert(pMapped.Get());
			assert(context);
			HR2Ex(context->m_d3dContext->Map( pMapped.Get(), 0, D3D11_MAP_READ, 0, &mapped ));
		}
		void Unmap()
		{
			if (mapped.pData && context)
			{
				assert(pMapped.Get());
				context->m_d3dContext->Unmap(pMapped.Get(), 0);
			}
			mapped.pData = nullptr;
		}
	};

#ifdef _DEBUG
	class DebugObject
	{
		static volatile LONG count;
		LONG id;
		std::wstring name;

	public:
		~DebugObject();
		DebugObject(const wchar_t *text);
	};
#else
	class DebugObject
	{
	public:
		DebugObject(const wchar_t *text) { }
	};
#endif
}
